package nachos.threads;

import nachos.machine.*;

/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>,
 * and multiple threads can be waiting to <i>listen</i>. But there should never
 * be a time when both a speaker and a listener are waiting, because the two
 * threads can be paired off at this point.
 */
public class Communicator {

    /**
     * Used to synchronize the Condition2
     */
    Lock lock = new Lock();

    /**
     * The last word spoken by a speaker thread.
     */
    int currentWord;

    /**
     * A Thread that wants to speak goes to sleep on this condition
     * unless they are the only thread waiting to speak and there is a
     * listener available.
     */
    Condition2 speaking = new Condition2(lock);

    /**
     * A thread that wants to listen goes to sleep on this condition
     * unless it is the only thread ready to listen and there is a
     * speaker available.
     */
    Condition2 listening = new Condition2(lock);

    /**
     * The number of threads that are waiting to speak or currently in
     * the process of speaking.
     */
    int numSpeakers = 0;

    /**
     * The number of threads that are waiting to listen or currently
     * in the process of recieving a word.
     */
    int numListeners = 0;

    /**
     * Allocate a new communicator.
     */
    public Communicator() {
	
    }

    /**
     * Wait for a thread to listen through this communicator, and then transfer
     * <i>word</i> to the listener.
     *
     * <p>
     * Does not return until this thread is paired up with a listening thread.
     * Exactly one listener should receive <i>word</i>.
     *
     * @param	word	the integer to transfer.
     */
    public void speak(int word) {
	lock.acquire();

	// This counts me as a thread waiting to speak. The listener
	// who grabs my word will decrement this for me.
	numSpeakers++;

	// If there are threads lined up to speak, or if there are no
	// listeners, sleep. A listener will wake me up when it is
	// ready to listen.
	if (numSpeakers > 1 || numListeners == 0) {
	    speaking.sleep();
	}

	// At this point it is my turn to speak and there is a
	// listener for me. Set the current word to my word, wake up
	// the current listener, and release the lock.
	currentWord = word;
	listening.wake();
	
	lock.release();
    }

    /**
     * Wait for a thread to speak through this communicator, and then return
     * the <i>word</i> that thread passed to <tt>speak()</tt>.
     *
     * @return	the integer transferred.
     */    
    public int listen() {

	lock.acquire();

	// This counts me as a thread that's ready to listen. I'll
	// decrement this when I'm done listening.
	numListeners++;

	// If there is a line of speakers and I am the first listener,
	// wake up a speaker.
	if (numListeners == 1 &&
	    numSpeakers > 0) {
	    speaking.wake();
	}

	// Wait for a speaker to give me a word.
	listening.sleep();

	// At this point, it is my turn to listen, and a speaker has
	// given me a word. Take the word and decrement the counters.
	int myWord = currentWord;
	--numListeners;
	--numSpeakers;

	// If there are more speakers and listeners waiting, wake up
	// the next speaker. If ther are more speakers but no more
	// listeners, the next thread that calls listen() will see
	// that there are speakers and wake one up. If there are more
	// listeners but no more speakers, the next thread that calls
	// speak() will see that there are listeners and wake one
	// up. If there are no listeners and no speakers, then we've
	// reset to the starting state.
	if (numListeners > 0 && numSpeakers > 0) {
	    speaking.wake();
	}

	lock.release();
	return myWord;
    }

    /**
     * This tests the Communicator class by spawning some producer and
     * consumer threads that communicate with each other. I was
     * verifying that it works by simply looking at the output, and
     * making sure that the right numbers are "said" and "heard".
     */
    public static void selfTest() {

	final Communicator c = new Communicator();
	
	final int wordsPerThread = 50;
	final int numThreadPairs = 5;
	KThread[] threads = new KThread[numThreadPairs * 2];

	// Spawn numThreadPairs threads each speaking wordsPerThread words.
	// Spwan numThreadPairs threads that each listen for wordsPerThread words.
	for (int i = 0; i < numThreadPairs; i++) {
	    final int start = i * wordsPerThread;
	    final int threadNum = i;
	    Runnable producer = new Runnable() {
		    public void run() {
			if (threadNum < 7) {
			    for (int j = 0; j < threadNum; j++) {
				ThreadedKernel.scheduler.increasePriority();
			    }
			}
			for (int j = 0; j < wordsPerThread; j++) {
			    int word = start + j;
			    //			    System.out.println("Saying " + word);
			    c.speak(word);
			}
		    }
		};
	    Runnable consumer = new Runnable() {
		    public void run() {
			for (int j = 0; j < wordsPerThread; j++) {
			    int word = c.listen();
			    //			    System.out.println("Heard " + word);
			}
		    }
		};
	    KThread pthread = new KThread(producer);
	    KThread cthread = new KThread(consumer);

	    pthread.setName("Producer " + i);
	    cthread.setName("Consumer " + i);
	    threads[2 * i] = cthread;
	    threads[2 * i + 1] = pthread;
	}

	// Fork all the threads first, then join to them to wait for them to finish.
	for (KThread t : threads) {
	    t.fork();
	}
	for (KThread t : threads) {
	    t.join();
	}
    }




}
